package com.sweet.pay.service;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import com.sweet.pay.conf.PayConfig;
import com.sweet.pay.dto.WxConfig;
import com.sweet.pay.dto.WxPayParam;
import com.sweet.pay.dto.WxPayResult;
import com.sweet.pay.enums.TradeType;
import com.sweet.pay.utils.TimeUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class PayService {

  private static final String WX_FAIL_XML = "<xml>\n" +
      "  <return_code><![CDATA[FAIL]]></return_code>\n" +
      "</xml>";

  private static final String WX_SUCCESS_XML = "<xml>\n" +
      "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
      "  <return_msg><![CDATA[OK]]></return_msg>\n" +
      "</xml>";

  private static final Logger log = LoggerFactory.getLogger(PayService.class);

  @Autowired
  private PayConfig payConfig;

  public WxPayResult wxpay(WxPayParam param) {
    final WxPayResult result = new WxPayResult();
    WXPay wxpay = null;
    try {
      wxpay = new WXPay(new WxConfig(payConfig.wxAppId, payConfig.wxMchId,
          payConfig.wxKey, payConfig.wxCertPath));
    } catch (Exception e) {
      log.error("createWxPayError: ", e);
      result.setThrowable(e);
      return result;
    }

    Map<String, String> data = new HashMap<String, String>();
    data.put("body", param.getBody());
    data.put("out_trade_no", param.getOrderNo());
    data.put("fee_type", "CNY");
    data.put("total_fee", param.getTotalFee());
    data.put("spbill_create_ip", payConfig.serverAddress);
    data.put("notify_url", payConfig.wxNotifyUrl);
    data.put("trade_type", param.getTradeType().name());  // 此处指定为APP支付
    if (TradeType.NATIVE == param.getTradeType()) {
      data.put("product_id", param.getProductId());
    }

    Map<String, String> resp = null;
    try {
      resp = wxpay.unifiedOrder(data);
      log.debug("wxPayResp: " + resp);
    } catch (Exception e) {
      log.error("wxPayError: ", e);
      result.setThrowable(e);
      return result;
    }

    if ("SUCCESS".equals(resp.get("return_code"))) {
      if ("SUCCESS".equals(resp.get("result_code"))) {
        return new WxPayResult(resp, param.getTradeType());
      } else {
        log.error("wxPayRespError: " + resp);
        result.setThrowable(new RuntimeException(resp.get("err_code_des")));
        return result;
      }
    } else {
      log.error("wxPayRespError: " + resp);
      result.setThrowable(new RuntimeException(resp.get("return_msg")));
      return result;
    }
  }

  public boolean wxPayNotify(String body) {
    // 1. xml => map
    Map<String, String> notifyMap = null;
    try {
      notifyMap = WXPayUtil.xmlToMap(body);
    } catch (Exception e) {
      log.error("wechat解析xml报错：", e);
      return false;
    }

    // 2. 校验数据
    String key = payConfig.wxKey;
    if (!isPayResultNotifySignatureValid0(key, new HashMap<>(notifyMap))) {
      log.error("wechatValidError key: " + key + ", notify: " + notifyMap);
      return false;
    }

    // 3. 校验订单是否已经处理
    synchronized (this) {
      if (!payConfig.wxAppId.equals(notifyMap.get("appid"))) {
        log.error("wechatAppIdNotEq notifyAppId: " + notifyMap.get("appid") + ", appId: " + payConfig.wxAppId);
        return false;
      }

      return wxPayNotifyService.notify(notifyMap.get("transaction_id"), notifyMap.get("out_trade_no"),
          Integer.parseInt(notifyMap.get("total_fee")), formatTime(notifyMap.get("time_end")),
          TradeType.valueOf(notifyMap.get("trade_type")));
    }
  }

  private static long formatTime(String timeEnd) {
    return TimeUtils.dateTimeToTimestamp(TimeUtils.strToLocalDateTime(timeEnd, "yyyyMMddHHmmss"));
  }

  @Autowired
  private WxPayNotifyService wxPayNotifyService;

  private static boolean isPayResultNotifySignatureValid0(String key0, HashMap<String, String> notifyMap) {
    final String sign = notifyMap.get("sign");
    notifyMap.remove("sign");

    String s = notifyMap.keySet().stream().sorted().map(key -> key + "=" + notifyMap.get(key)).collect(Collectors.joining("&"));
    s += "&key=" + key0;
    String md5 = "";
    try {
      md5 = WXPayUtil.MD5(s);
      return md5.equals(sign);
    } catch (Exception e) {
      log.error("wechatMD5加密失败", e);
      log.error("wechatnotifyParam: " + notifyMap);
      log.error("wechatMD5Value: " + s + ", sign: " + md5);
      return false;
    }
  }

  public String getReqBody(HttpServletRequest request) {
    ServletInputStream inputStream = null;
    String body = null;
    try {
      inputStream = request.getInputStream();
      body = IOUtils.toString(inputStream);
    } catch (IOException e) {
      log.error("wechatGetBodyError: ", e);
    } finally {
      if (inputStream != null) {
        try {
          inputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    return body;
  }
}
